'''
Created on Feb 11, 2011

@author: Jonathan Herbst
'''

import subprocess, os, re
from threading import Thread, Timer

from Utilities import ClearAfterWaitEvent, LogFile
import Messages

# string format for the location of a GPIO file
gpioLocFormat = "%sGPIO%s"

# the location of the door latch gpio file
doorLatchGPIOLoc = None
redLEDLoc = None
yellowLEDLoc = None
greenLEDLoc = None
motionLEDLoc = None
btLEDLoc = None

def InitGPIOInterface(config):
    global doorLatchGPIOLoc
    global btLEDLoc
    global redLEDLoc
    global yellowLEDLoc
    global greenLEDLoc
    global motionLEDLoc
    
    if config.gpioDirectory is not None:
        # 
        baseGPIOLoc = str(config.gpioDirectory)
        if baseGPIOLoc[-1] is not '/':
            baseGPIOLoc += '/'
        
        # setup the door latch gpio file location
        doorLatchGPIOLoc = gpioLocFormat % (baseGPIOLoc, config.doorLatchGPIO)
        # setup the motion detector gpio file location
        
        # setup the LED gpio file locations
        btLEDLoc = gpioLocFormat % (baseGPIOLoc, config.btResetLEDGPIO)
        redLEDLoc = gpioLocFormat % (baseGPIOLoc, config.redLEDGPIO)
        yellowLEDLoc = gpioLocFormat % (baseGPIOLoc, config.yellowLEDGPIO)
        greenLEDLoc = gpioLocFormat % (baseGPIOLoc, config.greenLEDGPIO)
        motionLEDLoc = gpioLocFormat % (baseGPIOLoc, config.motionDetectedLEDGPIO)
        print motionLEDLoc
    
class GPIONotInitializedException(Exception): pass

class GPIO():
    # string to write to gpio file to set output high
    GPIO_OUT_SET = "GPIO out set"
    # string to write to gpio file to set output low
    GPIO_OUT_CLEAR = "GPIO out clear"
    
class GPI(Thread):
    GPIO_IN = "AF1 in"
    GPIO_EVENT_DEV = "/dev/gpio-event"
    
    _gpioEventRE = re.compile("(\d+) ([RF]) (\d+\.\d+)")
    
    def __init__(self):
        self.m_bindings = {}
        Thread.__init__(self)
        self.daemon = True
        self.start()
    
    def Bind(self, gpioPin, callback, mode='b', debounce=20):
        try:
            gpioPin = int(gpioPin)
            if not hasattr(callback, '__call__'):
                return False
            #self._InitGPIPin(gpioPin)
            #p = subprocess.Popen(["gpio-event", "%s:%s:%s" % (gpioPin, mode, debounce)])
            #os.waitpid(p.pid, 0)
            self.m_bindings[gpioPin] = (mode, callback)
            return True
        except Exception, e:
            print e
            return False

    def run(self):
        try:
            #p = subprocess.Popen(["cat", "/dev/gpio-event"], stdout=subprocess.PIPE)
            #for line in p.stdout:
            #    print line
            #while True:
                
                """try:
                    f = open(self.GPIO_EVENT_DEV, 'r')
                    gpioStr = f.readline()
                    matchObject = self._gpioEventRE.match(gpioStr)
                    if matchObject:
                        eventInfo = (int(matchObject.group(1)), str(matchObject.group(2)), float(matchObject.group(3)))
                        if eventInfo[0] in self.m_bindings:
                            self.m_bindings[eventInfo[0]][1](eventInfo)
                    f.close()
                except Exception, e:
                    print e"""
        except:
            pass
        
    def _InitGPIPin(self, pin):
        f = open(gpioLocFormat % ('/proc/gpio/', pin), 'w')
        f.write(self.GPIO_IN)
        f.flush()
        f.close()
    
class DoorLatch(GPIO, Thread):
    
    def __init__(self, unlockCallback):
        self.m_unlockCallback = unlockCallback
        
        # thread control variables
        self.t_event = ClearAfterWaitEvent()
        self.t_exit = False
        self.t_doorLock = True
        
        # in thread only variables
        self.m_doorStatus = 0
        self.m_log = LogFile("DoorLatch")
        self.m_timeout = None
        
        self.m_log.Write("Init", "starting")
        
        if doorLatchGPIOLoc is None:
            self.m_log.Write("Init", "gpio not initialized", LogFile.WARNING)
            #raise GPIONotInitializedException()
        
        self._HLockDoor()
        
        Thread.__init__(self)
        self.start()
        
    def UnlockDoor(self, timeout = 30):
        self.m_log.Write("Door Latch", "Unlocking")
        self.t_doorLock = False
        if self.m_timeout:
            self.m_timeout.cancel()
        self.m_timeout = Timer(timeout, self._UnlockTimeout)
        self.m_timeout.start()
        self.t_event.set()
        
    def LockDoor(self):
        self.m_log.Write("Door Latch", "Locking")
        self.t_doorLock = True
        if self.m_timeout is not None:
            if self.m_timeout.isAlive():
                self.m_timeout.cancel()
                self.m_unlockCallback()
            self.m_timeout = None
        self.t_event.set()
        
    def Stop(self):
        self.t_exit = True
        self.LockDoor()
        
    def run(self):
        while not self.t_exit:
            self.t_event.wait()
            if self.t_doorLock:
                self._HLockDoor()
            else:
                self._HUnlockDoor()
        self._HLockDoor()
        
    def _UnlockTimeout(self):
        self.LockDoor()
        self.m_unlockCallback()
    
    def _HLockDoor(self):
        try:
            if self.m_doorStatus == 1:
                f = open(doorLatchGPIOLoc, "w")
                f.write(self.GPIO_OUT_CLEAR)
                f.close()
                self.m_doorStatus = 0
        except:
            self.m_log.Write("Door Latch", "Unable to lock door", LogFile.ERROR)
    
    def _HUnlockDoor(self):
        try:
            if self.m_doorStatus == 0:
                f = open(doorLatchGPIOLoc, "w")
                f.write(self.GPIO_OUT_SET)
                f.close()
                self.m_doorStatus = 1
        except:
            self.m_log.Write("Door Latch", "Unable to unlock door", LogFile.ERROR)
            
class LEDs(GPIO):
    
    def __init__(self):
        self.Red()
        self.MotionLED(False)
        self.BluetoothResetLED(False)
    
    def Green(self):
        if greenLEDLoc and redLEDLoc and yellowLEDLoc:
            self._LEDOn(greenLEDLoc)
            self._LEDOff(redLEDLoc)
            self._LEDOff(yellowLEDLoc)
    
    def Red(self):
        if greenLEDLoc and redLEDLoc and yellowLEDLoc:
            self._LEDOff(greenLEDLoc)
            self._LEDOn(redLEDLoc)
            self._LEDOff(yellowLEDLoc)
    
    def Yellow(self):
        if greenLEDLoc and redLEDLoc and yellowLEDLoc:
            self._LEDOff(greenLEDLoc)
            self._LEDOff(redLEDLoc)
            self._LEDOn(yellowLEDLoc)
    
    def MotionLED(self, on=True):
        print "motion led"
        if motionLEDLoc:
            if on:
                print "motion led on"
                self._LEDOn(motionLEDLoc)
            else:
                print "motion led off"
                self._LEDOff(motionLEDLoc)
    
    def BluetoothResetLED(self, on=True):
        if btLEDLoc:
            if on:
                self._LEDOn(btLEDLoc)
            else:
                self._LEDOff(btLEDLoc)
    
    def _LEDOn(self, gpioLoc):
        if gpioLoc is not None:
            f = open(gpioLoc, 'w')
            f.write(self.GPIO_OUT_SET)
            f.close()
        
    def _LEDOff(self, gpioLoc):
        if gpioLoc is not None:
            f = open(gpioLoc, 'w')
            f.write(self.GPIO_OUT_CLEAR)
            f.close()

class HardwareSimulator(Thread):
    """
    Class to simulate hardware functions
    
    Commands:
        bluetooth: b <message>
        sensor trigger: s
        pushbutton trigger: p
    """
    
    def __init__(self, controlStructure, startThread=True):
        Thread.__init__(self)
        
        self.t_exit = False
        self.t_controlStructure = controlStructure
        
        self.daemon = True
        
        if startThread:
            self.start()
    
    def run(self):
        self._CLProgram()
    
    def _CLProgram(self):
        while not self.t_exit:
            self._HandleCLMessage(raw_input())
    
    def _HandleCLMessage(self, message):
        try:
            message = str(message)
            if message[0] is "h":
                self._HandleHelp()
            elif message == "exit" or message == "quit":
                print "exit"
                self._HandleExit()
            elif message[0] is "s":
                self._HandleSensorEvent()
            elif message[0] is "p":
                self._HandlePushbuttonEvent()
            elif message[0] is "b":
                self._HandleBluetoothEvent(message)
            else:
                self._HandleBadCLMessage(message)
        except:
            # if we get an exception just do nothing
            pass
            
    def _HandleExit(self):
        self.t_exit = True
        self.t_controlStructure.TriggerExit()
        
    def _HandleSensorEvent(self):
        self.t_controlStructure.TriggerAlert()
        
    def _HandlePushbuttonEvent(self):
        self.t_controlStructure.Push(Messages.PBDoorCommand(Messages.PBDoorCommand.GRANTACCESSACTION))
        
    def _HandleBluetoothEvent(self, message):
        if len(message) > 3:
            message = Messages.parseMessage(message[2:])
            if message.__class__ in Messages.ACCEPTABLE_FROM_BLUETOOTH:
                self.t_controlStructure.Push(message)
            
    def _HandleBadCLMessage(self, message):
        pass
    
    def _HandleHelp(self):
        pass
        
def test_DoorLatch():
    global doorLatchGPIOLoc
    
    doorLatchGPIOLoc = "/proc/gpio/GPIO61"
    f = open(doorLatchGPIOLoc, 'w')
    f.write(GPIO.GPIO_OUT_CLEAR)
    f.close()
    
    doorLatch = DoorLatch()
    
    exit = False
    while not exit:
        ip = raw_input()
        if ip is 'q':
            exit = True
        else:
            timeout = int(ip)
            doorLatch.UnlockDoor(timeout)
    
    doorLatch.Stop()
        
if __name__ == "__main__":
    test_DoorLatch()
